package com.zazworka.jira.rest;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import com.atlassian.jira.bc.issue.search.SearchService;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.search.SearchException;
import com.atlassian.jira.jql.builder.JqlClauseBuilder;
import com.atlassian.jira.jql.builder.JqlQueryBuilder;
import com.atlassian.jira.project.Project;
import com.atlassian.jira.project.ProjectManager;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.project.version.VersionManager;
import com.atlassian.jira.web.bean.PagerFilter;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import com.zazworka.jira.rest.SVNCommentsRestResource.SVNQAReport;
import com.zazworka.jira.rest.SVNCommentsRestResource.TimeReport;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNWCUtil;



/**
 * A resource of message.
 */
@Path("/tagging")
public class TaggingQARestResource {

	private SVNRepository repository;
	
	/**
	 * maximal number of retries of subversion operations 
	 */
	private int maxTries=2;
	
	private final ProjectManager projectManager;
	private final VersionManager versionManager;
	
	
	public TaggingQARestResource(ProjectManager projectManager, VersionManager versionManager){
		this.projectManager = projectManager;
		this.versionManager = versionManager;
	}
	
	@SuppressWarnings("deprecation")
	@GET
    @AnonymousAllowed
    @Path("tagging")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Response getMessage(
    		@QueryParam ("svnURL") String svnURL, 
    		@QueryParam ("svnPath") String svnPath, 
    		@QueryParam ("svnUser") String svnUser,
    		@QueryParam ("svnPassword") String svnPassword,
    		@QueryParam ("projectId") String projectId
    	)
    {
		try{
    		repository = connectToSVN(svnURL, svnPath, svnUser, svnPassword);
        	Long revisionNumber = repository.getLatestRevision();
        	
        	//list of files and directories
        	LinkedList<String> listOfFiles = checkoutSVNRevision(revisionNumber);
			
        	//list of jira versions
        	Project p = projectManager.getProjectObjByKey(projectId);
        	if(p==null){
        		return Response.ok(new SVNTaggingQAReport("Error: Project ID '"+projectId+"' cannot be found!",null)).build();
        	}
        	List<Version> versions = versionManager.getVersions(p);
        	
        	SVNTaggingQAReport report = new SVNTaggingQAReport("",new ArrayList<TaggingQARestResource.VersionReport>());
        	//match up JIRA version and tags
        	for(Version v : versions){
        		String tags = "";
        		for(String t : listOfFiles){
        			if(v.getName().equals(t)){
        				tags += t+" ";
        			}
        		}
        		report.getVersionReports().add(new VersionReport(v.getName(),v.isReleased(),dateToLabel(v.getReleaseDate()),tags));
        	}
        	
        	//report tags that cannot be assigned to JIRA versions
        	for(String t : listOfFiles){
        		boolean found = false;
        		for(Version v :versions){
        			if(v.getName().equals(t)){
        				found=true;
        				break;
        			}
        		}
        		if(!found){
        			report.getVersionReports().add(new VersionReport(null,false,null,t));
        		}
        	}
        	
        		
        	
        	return Response.ok(report).build();
        	
    	}
    	catch(Exception e) {
    		return Response.ok(new SVNQAReport(e.getMessage()+"\n"+Arrays.toString(e.getStackTrace()), null)).build(); 
    	}
		
		
    }
	
	@SuppressWarnings("deprecation")
	private String dateToLabel(Date releaseDate) {
		if(releaseDate != null){
			return ""+(releaseDate.getMonth()+1)+"/"+(releaseDate.getDate())+"/"+(releaseDate.getYear()+1900);
		}
		else{
			return "no release date set";
		}
	}

	private SVNRepository connectToSVN(String svnURL, String svnPath, String svnUser, String svnPassword) throws SVNException{
    	
    	SVNRepository repository = null;
    	
    	DAVRepositoryFactory.setup();

		System.out.println(svnURL);
		repository = SVNRepositoryFactory.create(SVNURL
				.parseURIEncoded(svnURL+"/"+svnPath));
		// create authentification object
		ISVNAuthenticationManager authManager = SVNWCUtil
				.createDefaultAuthenticationManager(svnUser,svnPassword);
		repository.setAuthenticationManager(authManager);
		
		return repository;
    }
	
	/**
	 * Checkouts the contents of the repository at a given revision.
	 * If path is set then only this directory will be checked out.
	 * 
	 * @param revisionNumber: the number of the revision to be checked out.
	 * @return: a list of full path names of all existing dirs and files at that point.
	 * @throws SVNException
	 */
	public LinkedList<String> checkoutSVNRevision(Long revisionNumber){
		
		//agenda-based algorithm that reads contents of one dir and appends 
		//all its subfolders to the agenda
		
		//list for storing SVNDirEntries
		LinkedList<SVNDirEntry> entityList = new LinkedList<SVNDirEntry>();
		//list for their relative paths 
		LinkedList<String> entityPathList = new LinkedList<String>();
		
		//get enities from SVN path and store in entityList
		SVNNodeKind nodeKind = null;
		boolean success = false;
		int tries = 0;
		while(!success && tries < maxTries){
			try{
				tries++;
				repository.getDir("", revisionNumber, false, entityList);
				success = true;
			}
			catch(SVNException e){
				System.out.print("e1");
				System.out.println(e.getMessage());
				continue;
			}
		}
		
		for(SVNDirEntry temp :entityList){
			entityPathList.add(temp.getRelativePath());
		}
		
		return entityPathList;
	}
	
	 @XmlRootElement
	    @XmlAccessorType(XmlAccessType.FIELD)
	    public static class SVNTaggingQAReport {

	        @XmlElement
	        private String errorMessage;
	        
	        @XmlElement
	        private ArrayList<VersionReport> versionReports;

	        public SVNTaggingQAReport(String errorMessage, ArrayList<VersionReport> versionReports) {
	        	this.errorMessage=errorMessage;
	        	this.versionReports=versionReports;
	        }
	        
	        private SVNTaggingQAReport(){}

	        public String getErrorMessage(){
	        	return this.errorMessage;
	        }
	        
	        public ArrayList<VersionReport> getVersionReports(){
	        	return this.versionReports;
	        }
	        
	    }
	 
	 @XmlRootElement
	    @XmlAccessorType(XmlAccessType.FIELD)
	    public static class VersionReport {

	        @XmlElement
	        private String JIRAVersion;
	        
	        @XmlElement
	        private Boolean released;
	        
	        @XmlElement
	        private String releaseDate;
	        
	        @XmlElement
	        private String SVNTags;

	        public VersionReport(String JIRAVersion, Boolean released, String releaseDate, String SVNTags) {
	        	this.JIRAVersion=JIRAVersion;
	        	this.released=released;
	        	this.releaseDate=releaseDate;
	        	this.SVNTags=SVNTags;
	        }
	        
	        private VersionReport(){}

	        public String getJIRAVersion(){
	        	return this.JIRAVersion;
	        }
	        
	        public String getSVNTags(){
	        	return this.SVNTags;
	        }
	        
	        public Boolean getReleased(){
	        	return released;
	        }
	        
	        public String getReleaseDate(){
	        	return releaseDate;
	        }
	        
	    }
}